package com.zjy.basic.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.zjy.basic.commonenum.ErrorCode;
import com.zjy.basic.constant.Constant;
import com.zjy.basic.domain.dto.LoginDto;
import com.zjy.basic.domain.dto.WechatBinderDto;
import com.zjy.basic.exception.CustomException;
import com.zjy.basic.jwt.JwtUtils;
import com.zjy.basic.jwt.RsaUtils;
import com.zjy.basic.jwt.UserInfo;
import com.zjy.basic.service.LoginService;
import com.zjy.basic.util.AjaxResult;
import com.zjy.basic.util.HttpClientUtils;
import com.zjy.basic.util.Md5Utils;
import com.zjy.basic.util.StrUtils;
import com.zjy.system.domain.Menu;
import com.zjy.system.mapper.MenuMapper;
import com.zjy.system.mapper.PermissionMapper;
import com.zjy.user.domain.Logininfo;
import com.zjy.user.domain.User;
import com.zjy.user.domain.Wxuser;
import com.zjy.user.mapper.LogininfoMapper;
import com.zjy.user.mapper.UserMapper;
import com.zjy.user.mapper.WxuserMapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.FileCopyUtils;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class LoginServiceImpl implements LoginService {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private LogininfoMapper logininfoMapper;
    @Autowired
    private WxuserMapper wxuserMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private PermissionMapper permissionMapper;
    @Autowired
    private MenuMapper menuMapper;
    @Value("${jwt.rsa.pri}")// SPEL表达式读取配置文件中的值
    private String jwtRsaPrivate;

    /**
     * 账号密码登录
     * @param dto
     * @return
     */
    @Override
    @Transactional
    public AjaxResult account(LoginDto dto) {
        //通过填写的账号查询是否有这个用户(username or phone or email)
        Logininfo logininfo = logininfoMapper.findByUsername(dto.getUsername(),dto.getType());
        if (logininfo == null){
            throw new CustomException(ErrorCode.USER_NOT_FOUND.getCode());
        }
        //校验密码(把用户填写的用查询出来的salt加密和数据库加密后的密码进行比对)
        String salt = logininfo.getSalt();
        String password = Md5Utils.encrypByMd5(salt + dto.getPassword());
        if (!password.equals(logininfo.getPassword())){
            throw new CustomException(ErrorCode.PASSWORD_ERROR.getCode());
        }
        //存redis(key:uuid 值：logininfo)
//        String token = UUID.randomUUID().toString();
//        redisTemplate.opsForValue().set(token,logininfo,30, TimeUnit.MINUTES);
//        //组装数据返回给前端(map组装数据)
//        HashMap map = new HashMap<String, Object>();
//        map.put("token",token);
//        //处理logininfo
//        logininfo.setPassword(null);
//        logininfo.setSalt(null);
//        logininfo.setDisable(null);
//        map.put("logininfo",logininfo);
        return AjaxResult.me().setResultObj(loginSuccessHandler(logininfo));
    }

    /**
     * 微信登录
     * @param map
     * @return
     */
    @Override
    @Transactional
    public AjaxResult wechatLogin(Map<String, String> map) {
        //校验参数判断code是否传递过来
        String code = map.get("code"); //先获取嘛
        if (code == null){
            throw new CustomException(ErrorCode.WECHAT_GG.getCode());
        }
        //通过code请求微信获取token
        String tokenUrl = Constant.WeChat.GET_TOKEN
                .replace("APPID",Constant.WeChat.APPID)
                .replace("SECRET",Constant.WeChat.SECRET)
                .replace("CODE",code);
        //发送请求获取token(调用获取token的接口)
        String result = HttpClientUtils.httpGet(tokenUrl);
        //把结果返回值转成json对象
        JSONObject jsonObject = JSONObject.parseObject(result);
        String access_token = jsonObject.getString("access_token");
        String openid = jsonObject.getString("openid");
        //使用openid查询本地是否有关联此openid的用户
        Wxuser wxuser = wxuserMapper.findByOpenid(openid);
        //判断wxuser是否存在
        if (wxuser == null){
            //注册流程  ?access_token=xxxxx&openid=xxxxx
            String url = "?access_token="+access_token+"&openid="+openid;
            return AjaxResult.me().setResultObj(url).setMsg("binder");
        }
        //如果存在，直接给他免密登录
        Long userId = wxuser.getUserId();
        Logininfo logininfo = userMapper.findLogininfoByUserId(userId);

//        //还是进行存储登录信息的操作(登录业务都有这下面的操作)
//        //存redis(key:uuid 值：logininfo)
//        String token = UUID.randomUUID().toString();
//        redisTemplate.opsForValue().set(token,logininfo,30, TimeUnit.MINUTES);
//        //组装数据返回给前端(map组装数据)
//        HashMap hm = new HashMap<String, Object>();
//        hm.put("token",token);
//        //处理logininfo
//        logininfo.setPassword(null);
//        logininfo.setSalt(null);
//        logininfo.setDisable(null);
//        hm.put("logininfo",logininfo);
        return AjaxResult.me().setResultObj(loginSuccessHandler(logininfo));
    }

    /**
     * 微信绑定
     * @param dto
     * @return
     */
    @Override
    @Transactional
    public AjaxResult binder(WechatBinderDto dto) {
        //判断两次密码是否一致
        if (!dto.getPassword().equals(dto.getConfirmPassword())){
            throw new CustomException(ErrorCode.CONFIRMPASSWORD_ERROR.getCode());
        }
        //判断用户是否已经存在
        User user1 = userMapper.findByPhone(dto.getUsername());
        if (user1!=null){//如果用户能查询出来，代表已注册
            throw new CustomException(ErrorCode.USER_REGISTERED.getCode());
        }
        //保存注册信息三个表 logininfo user wxuser，发送请求获取微信基本信息
        String getUserInfoUrl = Constant.WeChat.GET_USER_INFO
                .replace("ACCESS_TOKEN",dto.getToken())
                .replace("OPENID",dto.getOpenId());
        //发送请求获取信息
        String result = HttpClientUtils.httpGet(getUserInfoUrl);
        //封装要保存的对象logininfo user wxuser
//        String salt = StrUtils.getComplexRandomString(32);
//        String password = Md5Utils.encrypByMd5(salt + dto.getPassword());
        //封装好了user对象
        User user = map2User(dto.getUsername(),dto.getPassword());
        //封装好了loginInfo对象
        Logininfo logininfo = user2Logininfo(user);
        //先保存loginInfo对象，并获取自增长id(user对象需要添加loginInfoId)
        logininfoMapper.add(logininfo);
        //保存user对象
        user.setLogininfoId(logininfo.getId());
        userMapper.add(user);
        //封装wxuser对象
        Wxuser wxuser = JSONObject.parseObject(result, Wxuser.class); //这个方法可以将json字符串直接转换为指定对象
        wxuser.setUserId(user.getId());
        wxuserMapper.add(wxuser);

        //免密登录
        //还是进行存储登录信息的操作(登录业务都有这下面的操作)
        //存redis(key:uuid 值：logininfo)
//        String token = UUID.randomUUID().toString();
//        redisTemplate.opsForValue().set(token,logininfo,30, TimeUnit.MINUTES);
//        //组装数据返回给前端(map组装数据)
//        HashMap hm = new HashMap<String, Object>();
//        hm.put("token",token);
//        //处理logininfo
//        logininfo.setPassword(null);
//        logininfo.setSalt(null);
//        logininfo.setDisable(null);
//        hm.put("logininfo",logininfo);
        return AjaxResult.me().setResultObj(loginSuccessHandler(logininfo));
    }

    private User map2User(String phone, String password) {
        User user = new User();
        user.setUsername(phone);
        user.setPhone(phone);
        //获取复杂随机数作为盐值
        String salt = StrUtils.getComplexRandomString(32);
        //密码加盐加密
        String md5Pwd = Md5Utils.encrypByMd5(salt+password);
        user.setSalt(salt);
        user.setPassword(md5Pwd);
        user.setState(1);//正常
        return user;
    }

    private Logininfo user2Logininfo(User user) {
        Logininfo logininfo = new Logininfo();
        //底层会按照属性同名原则进行属性的拷贝：id,username,phone,email,salt,password
        BeanUtils.copyProperties(user,logininfo);
        logininfo.setType(1);
        logininfo.setDisable(1);
        return logininfo;
    }

    /**
     * 登录成功处理
     * @param logininfo
     * @return
     */
    private Map<String, Object> loginSuccessHandler(Logininfo logininfo) {
        UserInfo userInfo = new UserInfo();
        Map<String, Object> map = new HashMap<>();
        userInfo.setLogininfo(logininfo);
        //判断当前用户是主站还是后端管理系统
        if(logininfo.getType() == 1){
            //单纯返回个人信息
            try {
                PrivateKey privateKey = RsaUtils
                        .getPrivateKey(FileCopyUtils
                                .copyToByteArray(this.getClass().getClassLoader().getResourceAsStream(jwtRsaPrivate)));
                //加密得到token
                String token = JwtUtils.generateTokenExpireInMinutes(userInfo, privateKey, 60*24);
                logininfo.setPassword(null);
                logininfo.setSalt(null);
                map.put("logininfo",logininfo);
                map.put("token",token);
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            } catch (InvalidKeySpecException e) {
                throw new RuntimeException(e);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }else{
            //如果是后端，返回个人信息+权限信息
            List<String> sns = permissionMapper.findPermissionByLoginInfoId(logininfo.getId());
            List<Menu> menus = menuMapper.findMenuByLoginInfoId(logininfo.getId());
            userInfo.setPermissions(sns);
            userInfo.setMenus(menus);
            try {
                //获取私钥
                PrivateKey privateKey = RsaUtils
                        .getPrivateKey(FileCopyUtils
                                .copyToByteArray(this.getClass().getClassLoader().getResourceAsStream(jwtRsaPrivate)));
                //加密得到token
                String token = JwtUtils.generateTokenExpireInMinutes(userInfo, privateKey, 60*24);
                logininfo.setPassword(null);
                logininfo.setSalt(null);
                map.put("logininfo",logininfo);
                map.put("token",token);
                map.put("permissions",sns);
                map.put("menus",menus);
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            } catch (InvalidKeySpecException e) {
                throw new RuntimeException(e);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return map;
    }

}
